

import { toIndex } from '../numbers/helpers';
import { getProp, deepEqual } from '../objects/helpers';
import { isArray } from '../types';

/**
 * 检查对象是否在数组中（支持根据键值比较或深度比较）
 * @param {Record<string, any>} item - 要检查的对象
 * @param {Record<string, any>[]} array - 要搜索的数组
 * @param {string | ((item: Record<string, any>) => PropertyKey) | undefined} key - 对象键配置，用于比较对象是否相等
 * @returns {boolean} 如果对象在数组中，返回true，否则返回false
 * @example
 * // 使用字符串键进行比较
 * const isInArray1 = objectInArray({ id: 1, name: '张三' }, [{ id: 1, name: '张三' }], 'id'); // 返回 true
 * 
 * // 使用函数键进行比较
 * const isInArray2 = objectInArray(
 *   { id: 2, name: '李四' },
 *   [{ id: 2, name: '李四' }],
 *   item => item.id
 * ); // 返回 true
 * 
 * // 不使用键（进行深度比较）
 * const isInArray3 = objectInArray({ id: 3, name: '王五' }, [{ id: 3, name: '王五' }]); // 返回 true
 */
export function objectInArray(
  item: Record<string, any>,
  array: Record<string, any>[],
  key?: string | ((item: Record<string, any>) => PropertyKey)
): boolean {
  return array.some(arrayItem => {
    if (key) {
      if (typeof key === 'function') {
        return key(arrayItem) === key(item);
      }
      return getProp(arrayItem, key as string) === getProp(item, key as string);
    }
    // 进行深度比较，而不是简单的引用比较
    return deepEqual(arrayItem, item);
  });
}

/**
 * 递归地对数组中的每个项应用回调函数
 * @param item 要处理的项
 * @param callback 回调函数
 * @returns 处理后的项
 */
export function callRecursively(item: any, callback?: (item: any) => any): any {
  if (!callback) return item;

  // 先对当前项应用回调
  const processedItem = callback(item);

  // 如果处理后的结果是数组，递归处理其中的每个元素
  if (Array.isArray(processedItem)) {
    return processedItem.map(subItem => callRecursively(subItem, callback));
  }

  return processedItem;
}

/**
 * 将数组扁平化到指定层数
 * @param arr 要扁平化的数组
 * @param depth 扁平化的层数，默认为0（表示无限扁平）
 * @param callback 可选的回调函数，会对每个处理的item调用
 * @returns 扁平化后的数组，如果输入不是数组则返回原输入
 */
export function flatten(arr: any, depth: number = 0): any[] {
  // 检查输入是否为数组
  if (!Array.isArray(arr)) {
    return [arr];
  }

  // 如果深度为0或负数，表示无限扁平化
  if (depth <= 0) {
    return arr.reduce((acc: any[], item: any) => {
      return acc.concat(Array.isArray(item) ? flatten(item, 0) : item);
    }, []);
  }

  // 按指定深度扁平化
  return arr.reduce((acc: any[], item: any) => {
    // 当depth为1时，直接concat item而不再递归
    return acc.concat(depth === 1 ? item : (Array.isArray(item) ? flatten(item, depth - 1) : item));
  }, []);
}

/**
 * 将记录数组按照指定的字段分组
 * @param records 记录数组
 * @param field 分组的字段名
 * @returns 分组后的对象，键为字段值，值为该分组下的记录数组
 * @example
 * // 返回 { 'class1': [{id: 1, name: '张三', class: 'class1'}, ...], 'class2': [...] }
 * groupBy([{id: 1, name: '张三', class: 'class1'}, {id: 2, name: '李四', class: 'class2'}], 'class');
 */
export function groupBy<T extends Record<string, any>>(records: T[], field: string): Record<string, T[]> {
  // 检查输入是否为数组
  if (!Array.isArray(records)) {
    throw new Error('records must be an array');
  }

  // 检查字段名是否有效
  if (!field || typeof field !== 'string') {
    throw new Error('field must be a non-empty string');
  }

  // 使用reduce进行分组
  return records.reduce((groups: Record<string, T[]>, record: T) => {
    // 获取分组字段的值
    const key = record[field];

    // 确保分组数组存在
    if (!groups[key]) {
      groups[key] = [];
    }

    // 将当前记录添加到对应的分组
    groups[key].push(record);

    return groups;
  }, {});
}

/**
 * 获取数组中的元素
 * @param array 数组
 * @param index 索引
 * @returns 包含键名、值和是否存在的对象
 */
export function itemAt(array: any, index: number): { key: number | string | undefined, value: any, exists: boolean } {
  if (!Array.isArray(array) && typeof array !== 'object') {
    return { key: undefined, value: undefined, exists: false };
  }

  if (Array.isArray(array)) {
    if (index >= 0 && index < array.length) {
      return { key: index, value: array[index], exists: true };
    }
  } else {
    // 处理对象
    const keys = Object.keys(array);
    if (index >= 0 && index < keys.length) {
      const key = keys[index];
      return { key: index, value: array[key], exists: true };
    }
  }

  return { key: undefined, value: undefined, exists: false };
}

/**
 * 判断数组是否为空
 * @param array 要检查的数组
 * @returns 是否为空
 */
export function isEmpty(array: any): boolean {
  if (!Array.isArray(array) && typeof array !== 'object') {
    return true;
  }

  if (Array.isArray(array)) {
    return array.length === 0;
  }

  return Object.keys(array).length === 0;
}

/**
 * 判断数组是否不为空
 * @param array 要检查的数组
 * @returns 是否不为空
 */
export function isNotEmpty(array: any): boolean {
  return !isEmpty(array);
}

/**
 * 从数组中移除指定元素或元素数组
 * @param {array} array 数组
 * @param {any|any[]} element 要移除的单个元素或元素数组（不是数组本身作为单个元素）
 * @returns 如果array不是数组，返回false；否则返回被删除元素组成的数组
 */
export function remove(array: any[], element: any | any[]): any[] | false {
  // 如果array不是数组，返回false
  if (!Array.isArray(array)) {
    return false;
  }

  // 将单个元素转换为数组，统一处理逻辑
  const elementsToRemove = isArray(element) ? element : [element];
  const removedElements: any[] = [];

  // 使用Set提高查找效率
  const elementsSet = new Set(elementsToRemove);

  // 创建一个新数组，只包含不需要删除的元素
  const filteredArray = array.filter(item => {
    if (elementsSet.has(item)) {
      // 收集被删除的元素
      removedElements.push(item);
      return false;
    }
    return true;
  });

  // 清空原数组并添加过滤后的元素
  array.length = 0;
  array.push(...filteredArray);

  return removedElements;
}

/**
 * 合并两个数组，去除重复元素
 * @param arr1 第一个数组
 * @param arr2 第二个数组
 * @returns 合并后的数组
 */
export function mergeUnique(arr1: any[], arr2: any[]): any[] {
  if (!Array.isArray(arr1)) arr1 = [];
  if (!Array.isArray(arr2)) arr2 = [];

  const result = [...arr1];
  arr2.forEach(item => {
    if (!result.includes(item)) {
      result.push(item);
    }
  });
  return result;
}

/**
 * 默认排序函数，用于比较两个值
 * @param a 第一个比较值
 * @param b 第二个比较值
 * @param order 排序方向（'asc' 或 'desc'）
 * @returns 比较结果
 */
export function defaultSort(a: any, b: any, order: 'asc' | 'desc' = 'asc'): number {
  // 处理undefined和null
  if (a === undefined || a === null) return order === 'asc' ? 1 : -1;
  if (b === undefined || b === null) return order === 'asc' ? -1 : 1;

  // 处理数字
  if (typeof a === 'number' && typeof b === 'number') {
    return order === 'asc' ? a - b : b - a;
  }

  // 处理其他类型（转换为字符串后比较）
  const aStr = String(a);
  const bStr = String(b);

  return order === 'asc'
    ? aStr.localeCompare(bStr)
    : bStr.localeCompare(aStr);
}

/**
 * 对数组进行多条件排序
 * @param {T[]} array - 包含相同键值的对象数组
 * @param {Record<string, 'asc' | 'desc'>} sortOptions - 排序选项，键为对象的属性名，值为排序方向
 * @param {(a: any, b: any, key: string) => number} [compareCallback] - 自定义比较回调函数
 * @returns {T[]} - 排序后的新数组
 * @example
 * // 按年龄升序、姓名降序排序
 * const sortedArray = multiSort(
 *   [{ name: '张三', age: 25 }, { name: '李四', age: 22 }, { name: '王五', age: 25 }],
 *   { age: 'asc', name: 'desc' }
 * );
 * 
 * // 使用自定义比较函数
 * const customSorted = multiSort(
 *   [{ score: 85 }, { score: 92 }, { score: 78 }],
 *   { score: 'desc' },
 *   (a, b) => b - a
 * );
 */
export function multiSort<T extends Record<string, any>>(array: T[], sortOptions: Record<string, 'asc' | 'desc'>, compareCallback?: (a: any, b: any, key: string) => number): T[] {
  // 检查输入是否为数组
  if (!Array.isArray(array)) {
    return [];
  }

  // 创建数组副本以避免修改原数组
  const result = [...array];

  // 获取排序键数组
  const sortKeys = Object.keys(sortOptions);

  // 如果没有排序选项或数组为空，直接返回原数组副本
  if (sortKeys.length === 0 || result.length <= 1) {
    return result;
  }

  // 使用sort方法进行排序
  return result.sort((item1, item2) => {
    // 遍历所有排序条件，直到找到可以区分的条件
    for (const key of sortKeys) {
      const value1 = item1[key];
      const value2 = item2[key];

      // 使用自定义比较函数或默认比较函数
      const compareResult = compareCallback
        ? compareCallback(value1, value2, key)
        : defaultSort(value1, value2, sortOptions[key]);

      // 如果比较结果不为0，表示可以区分这两个元素
      if (compareResult !== 0) {
        return compareResult;
      }
    }

    // 所有条件都相同，保持原顺序
    return 0;
  });
}

/**
 * 检查值是否在数组中
 * @param {any} value 要查找的值
 * @param {any[]} array 要搜索的数组
 * @param {boolean} [isStrict=true] 是否使用严格比较（===），默认为true
 * @returns {boolean} 如果值在数组中，返回true，否则返回false
 * @example
 * // 返回 true
 * inArray(5, [1, 3, 5, 7, 9]);
 * 
 * // 返回 false（使用严格比较）
 * inArray('5', [1, 3, 5, 7, 9]);
 * 
 * // 返回 true（使用宽松比较）
 * inArray('5', [1, 3, 5, 7, 9], false);
 */
export function inArray(value: any, array: any[], isStrict: boolean = true): boolean {
  // 检查输入是否为数组
  if (!Array.isArray(array)) {
    return false;
  }

  // 使用严格比较
  if (isStrict) {
    return array.includes(value);
  }

  // 使用宽松比较（转换为字符串后比较）
  const valueStr = String(value);
  return array.some(item => String(item) === valueStr);
}

/**
 * 获取数组中指定索引位置的值
 * @param {any[]} array 要获取值的数组
 * @param {number} index 索引位置，可以是负数（从数组末尾开始计数）
 * @returns {any} 数组中指定索引位置的值，如果索引超出范围则返回undefined
 * @example
 * // 返回 3
 * valueAt([1, 2, 3, 4, 5], 2);
 * 
 * // 返回 5（使用负数索引从末尾开始计数）
 * valueAt([1, 2, 3, 4, 5], -1);
 * 
 * // 返回 undefined（索引超出范围）
 * valueAt([1, 2, 3], 5);
 */
export function valueAt(array: any[], index: number): any {
  // 检查输入是否为数组
  if (!Array.isArray(array)) {
    return undefined;
  }
  if (array.length === 0) {
    return undefined;
  }

  // 处理索引
  index = toIndex(array.length, index);

  // 返回指定索引位置的值
  return array[index];
}

/**
 * 获取数组的切片
 * @param {any[]} array 要获取切片的数组
 * @param {number} start 起始索引（包含），可以是负数
 * @param {number} [end] 结束索引（不包含），可以是负数，如果未指定则取到数组末尾
 * @returns {any[]} 数组的切片，如果输入不是数组则返回空数组
 * @example
 * // 返回 [2, 3, 4]
 * slice([1, 2, 3, 4, 5], 1, 4);
 * 
 * // 返回 [3, 4, 5]（使用负数起始索引）
 * slice([1, 2, 3, 4, 5], -3);
 * 
 * // 返回 [2, 3]（使用负数结束索引）
 * slice([1, 2, 3, 4, 5], 1, -2);
 */
export function slice(array: any[], start: number, end?: number): any[] {
  // 检查输入是否为数组
  if (!Array.isArray(array)) {
    return [];
  }

  const length = array.length;

  // 如果数组为空，返回空数组
  if (length === 0) {
    return [];
  }

  // 处理起始索引
  start = toIndex(length, start);

  // 处理结束索引
  if (end === undefined) {
    end = length;
  } else {
    end = toIndex(length, end);
  }

  // 确保索引在有效范围内
  start = Math.max(0, Math.min(length, start));
  end = Math.max(0, Math.min(length, end));

  // 确保start不大于end
  if (start >= end) {
    return [];
  }

  // 创建并填充切片数组
  const result: any[] = [];
  for (let i = start; i < end; i++) {
    result.push(array[i]);
  }

  return result;
}

/**
 * 基于内容的深度比较两个数组，不考虑元素顺序
 * @param {any[]} arr1 - 第一个要比较的数组
 * @param {any[]} arr2 - 第二个要比较的数组
 * @returns {boolean} 如果两个数组包含相同的元素（数量相同且内容相同，不考虑顺序），返回true，否则返回false
 * @example
 * // 返回 true
 * contentDeepEqual([1, 2, 3], [3, 2, 1]);
 * 
 * // 返回 false
 * contentDeepEqual([1, 2, 3], [1, 2]);
 * 
 * // 支持嵌套数组和对象的深度比较
 * contentDeepEqual(
 *   [{ id: 1, name: '张三' }, { id: 2, name: '李四' }],
 *   [{ id: 2, name: '李四' }, { id: 1, name: '张三' }]
 * ); // 返回 true
 */
export function contentDeepEqual(arr1: any[], arr2: any[]): boolean {
  // 检查输入是否为数组
  if (!Array.isArray(arr1) || !Array.isArray(arr2)) {
    return false;
  }

  // 如果数组长度不同，直接返回false
  if (arr1.length !== arr2.length) {
    return false;
  }

  // 对于空数组的情况
  if (arr1.length === 0) {
    return true;
  }

  // 创建一个标记数组，记录arr2中已经匹配的元素索引
  const matchedIndices = new Set<number>();

  // 遍历arr1中的每个元素，在arr2中查找匹配项
  for (const item1 of arr1) {
    let foundMatch = false;

    for (let i = 0; i < arr2.length; i++) {
      // 跳过已经匹配过的元素
      if (matchedIndices.has(i)) {
        continue;
      }

      // 使用deepEqual进行深度比较
      if (deepEqual(item1, arr2[i])) {
        // 标记这个索引已匹配
        matchedIndices.add(i);
        foundMatch = true;
        break;
      }
    }

    // 如果arr1中的某个元素在arr2中找不到匹配项，返回false
    if (!foundMatch) {
      return false;
    }
  }

  // 所有元素都找到了匹配项
  return true;
}